Skip to content

scrollable_mask: Keep rounded scroll-table corners clean at any scroll offset - #3400

Merged
huacnlee merged 1 commit into
mainfrom
ihavecoke/rounded-frame-cover
Oct 8, 2026
Merged

huacnlee merged 1 commit into
mainfrom
ihavecoke/rounded-frame-cover

Conversation

@ihavecoke

@ihavecoke ihavecoke commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Problem

#3309 rounds the table's first and last rows, which only lines up while the content sits at its unscrolled position. Once a wide table scrolls horizontally, gpui's rectangular content mask slices the clipped side square into the frame's rounded corner notches — the known limit noted in #3309.

Fix

Add RoundedFrameCover, a sibling overlay (like ScrollableMask) pinned to the viewport: it fills each corner notch — inside the frame's rect corner, outside the border arc — with a backdrop color, so the frame reads as a rounded clip at every scroll offset. The frame's own border paints after its children, so it stays crisp on top of both the scrolled content and the covers; nothing is repainted.

horizontal_scroll_area installs the cover from the caller style's corner radii, so markdown tables get it automatically. The element is exported from gpui-base — RoundedFrameCover::uniform(radius), .backdrop(color), .frame_border(width) — so application frames with their own rounded scroll viewports can reuse it; frame_border grows the notches out to the border box when the cover is a child of the bordered element itself.

Before

image

After

image

Test

  • corner_notch_paths_stay_inside_their_corner_squares pins the notch geometry: each path tessellates to an r-sized square anchored at its frame corner.
  • cargo nextest run -p gpui-base scrollable_mask — 17 passed.
  • cargo clippy -p gpui-base --locked -- --deny warnings / cargo fmt -- --check — clean.
  • Verified end-to-end in a consuming application via a local patch: markdown tables and an application-owned scrollable card keep rounded corners through horizontal scrolling.

A horizontally scrolled table clips with gpui's rectangular content
mask, so content poked square into the frame's rounded corner notches
once it scrolled — #3309 rounds the rows themselves, which only covers
the unscrolled position. Paint a RoundedFrameCover sibling over the
viewport: it fills each corner notch with the backdrop color, and the
frame's own border, painted after children, stays crisp on top.

The cover is exported from gpui-base so application tables with their
own rounded scroll frames can reuse it.
@ihavecoke ihavecoke changed the title text: Keep rounded scroll-table corners clean at any scroll offset scrollable_mask: Keep rounded scroll-table corners clean at any scroll offset Oct 8, 2026
@ihavecoke
ihavecoke marked this pull request as ready for review October 8, 2026 02:09
@huacnlee
huacnlee merged commit 72ec173 into main Oct 8, 2026
16 checks passed
@huacnlee
huacnlee deleted the ihavecoke/rounded-frame-cover branch October 8, 2026 03:04
linruohan pushed a commit to linruohan/gpui-component that referenced this pull request Oct 9, 2026
…l offset (longbridge#3400)

## Problem

longbridge#3309 rounds the table's first and last rows, which only lines up while
the content sits at its unscrolled position. Once a wide table scrolls
horizontally, gpui's rectangular content mask slices the clipped side
square into the frame's rounded corner notches — the known limit noted
in longbridge#3309.

## Fix

Add `RoundedFrameCover`, a sibling overlay (like `ScrollableMask`)
pinned to the viewport: it fills each corner notch — inside the frame's
rect corner, outside the border arc — with a backdrop color, so the
frame reads as a rounded clip at every scroll offset. The frame's own
border paints after its children, so it stays crisp on top of both the
scrolled content and the covers; nothing is repainted.

`horizontal_scroll_area` installs the cover from the caller style's
corner radii, so markdown tables get it automatically. The element is
exported from gpui-base — `RoundedFrameCover::uniform(radius)`,
`.backdrop(color)`, `.frame_border(width)` — so application frames with
their own rounded scroll viewports can reuse it; `frame_border` grows
the notches out to the border box when the cover is a child of the
bordered element itself.

### Before
<img width="308" height="217" alt="image"
src="https://github.com/user-attachments/assets/780173b3-7225-407b-bd79-6d2ed679a784"
/>

### After
<img width="276" height="273" alt="image"
src="https://github.com/user-attachments/assets/3f75bc6b-2be8-4d60-98b7-36c353b9541d"
/>


## Test

- `corner_notch_paths_stay_inside_their_corner_squares` pins the notch
geometry: each path tessellates to an r-sized square anchored at its
frame corner.
- `cargo nextest run -p gpui-base scrollable_mask` — 17 passed.
- `cargo clippy -p gpui-base --locked -- --deny warnings` / `cargo fmt
-- --check` — clean.
- Verified end-to-end in a consuming application via a local patch:
markdown tables and an application-owned scrollable card keep rounded
corners through horizontal scrolling.
@huacnlee huacnlee mentioned this pull request Oct 12, 2026
huacnlee added a commit that referenced this pull request Oct 12, 2026
## Description

Prepare v0.7.2 on `stable` from the PRs merged into `main` since v0.7.1,
leaving out every change that breaks existing consumers. `stable`
already carries #3371 and #3373; this adds the remaining compatible PRs
with `git cherry-pick -x` and bumps the workspace to 0.7.2.

### Included

- kit: Add optional gpui-fast backend (#3375)
- tooltip: Make show delay and grace period configurable with
`TooltipDefaults` (#3376)
- docs: Explain recovering focus when the focused element stops
rendering (#3386)
- diff: Add readonly patch viewer (#3378)
- kit: Use one Windows manifest with GPUI Fast (#3380)
- test: Make Progress and TitleBar findable in UI tests (#3383)
- input: Preserve selection, focus ring and context menu actions (#3382)
- diff: Add a Git history example and complete review APIs (#3388)
- text_view: Ignore gestures that stay inside one block plugin (#3390)
- text_view: Round the first and last table rows to the frame radius
(#3309)
- menu: Open a context menu with a long press on touch (#3393)
- text_view: Keep narrow table columns on one line (#3399)
- settings: Keep focus rings inside the list viewport (#3394)
- scrollable_mask: Keep rounded scroll-table corners clean at any scroll
offset (#3400)
- dialog: Add margin_top to AlertDialog (#3404)
- input: Add InputState::add_selection for programmatic multi-cursor
(#3403)
- text_selection: Stop writing selection state on every frame (#3407)
- table: Make the sort icon a labelled button (#3396)
- table: Report visible row ranges of one row or none (#3397)
- dialog: Support configurable entrance animations (#3405)
- docs: Explain the open_dialog builder and footer contracts (#3406)
- dock: Hand the focus to the tab that replaces a closed one (#3409)
- dropdown_button: Add A11y names for the menu trigger (#3410)
- text, resizable: Stop notifying state that did not change (#3418)
- editor: Add on-demand completion commands (#3411)
- table: Clear a right-clicked cell's outline on the next click (#3413)
- tab: Scroll a clicked tab into view with the next one peeking (#3419)
- table: Allow a header row height separate from the body (#3420)
- menu: Focus a context menu before its first frame (#3422)
- text: Preserve logical selection across presentation changes (#3421)
- text: Keep inline HTML mark highlights in Markdown (#3425)
- input: Anchor completion/code-action popups to the current frame's
caret (#3412)
- input: Run custom context menu builders after releasing the input
(#3433)
- input: Keep auto-grow rows fitted when the limits change (#3430)
- text: Paint TextView selection under the glyphs (#3439)

Already on `stable`: #3371, #3373.

### Excluded (Breaking Changes)

- #3432 motion: `Easing::sample` now returns values outside `[0, 1]` for
overshooting curves.
- #3415 tab: new `TabVariant::Folder` breaks exhaustive matches.
- #3402 kit: pointer helpers now keep held modifiers; nested
context-menu behavior changes in tests.
- #3384 test: `TestWindowExt` gains required methods.
- #3302 resizable: `ResizeHandle::on_drag` takes `&T` instead of
`Rc<T>`.
- #3389, #3395, #3398 webview: wrapper API changes, `gpui-wry` →
`gpui-webview` rename, and the GPUI Fast requirement.

#3411, #3405 and #3396 have a Breaking Changes section that reads "None"
and are included. #3376 only deprecates `TooltipRequest::placement`, and
#3397 / #3382 change behavior compatibly, so they are included too.

### Adaptations

- `crates/kit/tests/collections.rs` and `crates/kit/tests/rendering.rs`:
conflict resolutions drop the tests that belong to #3402 and keep each
backported PR's own test.
- `test: Keep backported tests on the v0.7.1 test API`: the menu and
completion tests from #3422 and #3412 used `find_all` and
`pointer_down`/`pointer_up` from #3384/#3402. They now query
`gpui_kit::base::test_support::snapshots` and dispatch mouse events
directly.
- `kit: Prepare v0.7.2`: workspace version bump; `Cargo.lock` only
updates workspace members.

## Public API

No new public API beyond what the included PRs add; each lists its items
in its own `## Public API` section (notably #3375, #3376, #3378, #3388,
#3403, #3404, #3405, #3411, #3420).

## How to Test

- `cargo clippy --workspace --all-targets -- --deny warnings` passes on
Linux.
- `cargo test -p gpui-kit --features "test-support component assets"
--test collections --test menu --test input` passes on Linux.
- The macOS-only `rendering` test (#3439 adds one case, the count is
updated to 11) was not run.

AI-assisted: the backport, conflict resolution and test adaptations were
done with Claude Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Floyd Wang <codelife721@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gustave <gustavel@gmail.com>
Co-authored-by: luren <84951845+lurenjia534@users.noreply.github.com>
Co-authored-by: Ylin <ylinwind@gmail.com>
Co-authored-by: ihavecoke <mixboip@gmail.com>
Co-authored-by: Lorenzo <150276837+Bombatomica64@users.noreply.github.com>
Co-authored-by: MR. XIE <113512030+xxww0098@users.noreply.github.com>
Co-authored-by: xxww0098 <shefkie1957@gmail.com>
Co-authored-by: Sola 🌙🤖 <266175376+sola-ryu@users.noreply.github.com>
Co-authored-by: Sola <sola-ryu@users.noreply.github.com>
Co-authored-by: Andy Hsu <i@nn.ci>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Floyd Wang <gassnake999@gmail.com>
Co-authored-by: Daiki Tomioka <20753105+TomiXRM@users.noreply.github.com>
Co-authored-by: Henrik Øgård <henrik@ogard.no>
Co-authored-by: Henrik Øgård <henrikogaard@users.noreply.github.com>
Co-authored-by: Endless <endless@longbridge.com>
Co-authored-by: John Costa <costajohnt@gmail.com>
Co-authored-by: Alexander <87220898+sneffets@users.noreply.github.com>
Co-authored-by: Cesar Talén <154442565+cesartalen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants